/*
	Copyright (C) 2009 Salil Bhagurkar
	
	This file is part of ViSE
	
	ViSE is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	ViSE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with ViSE.  If not, see <http://www.gnu.org/licenses/>.
*/

/*(Table 00234)
Values for disk operation status:
 00h	successful completion
 01h	invalid function in AH or invalid parameter
 02h	address mark not found
 03h	disk write-protected
 04h	sector not found/read error
 05h	reset failed (hard disk)
 05h	data did not verify correctly (TI Professional PC)
 06h	disk changed (floppy)
 07h	drive parameter activity failed (hard disk)
 08h	DMA overrun
 09h	data boundary error (attempted DMA across 64K boundary or >80h sectors)
 0Ah	bad sector detected (hard disk)
 0Bh	bad track detected (hard disk)
 0Ch	unsupported track or invalid media
 0Dh	invalid number of sectors on format (PS/2 hard disk)
 0Eh	control data address mark detected (hard disk)
 0Fh	DMA arbitration level out of range (hard disk)
 10h	uncorrectable CRC or ECC error on read
 11h	data ECC corrected (hard disk)
 20h	controller failure
 31h	no media in drive (IBM/MS INT 13 extensions)
 32h	incorrect drive type stored in CMOS (Compaq)
 40h	seek failed
 80h	timeout (not ready)
 AAh	drive not ready (hard disk)
 B0h	volume not locked in drive (INT 13 extensions)
 B1h	volume locked in drive (INT 13 extensions)
 B2h	volume not removable (INT 13 extensions)
 B3h	volume in use (INT 13 extensions)
 B4h	lock count exceeded (INT 13 extensions)
 B5h	valid eject request failed (INT 13 extensions)
 B6h	volume present but read protected (INT 13 extensions)
 BBh	undefined error (hard disk)
 CCh	write fault (hard disk)
 E0h	status register error (hard disk)
 FFh	sense operation failed (hard disk)
*/

#include <shared.h>


#define BD_INT 0x13
#define SECT_SZ 512

#define high(register) ((u8)(register>>8))

static int reset_disk(u8 drive)
{
	struct regs regs={0};
	regs.edx=drive;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high(((u16)regs.eax));
	}
	return 0;
}

static int reset_disk_retry(u8 drive,int retries)
{
	while(--retries>0)	{
		if((reset_disk(drive))==0)
			return 0;
	}
	return ELLERR;
}

#define DRIVE 0x80

struct drive_struct	{
	int cylinders;
	int sectors;
	int heads;
	u32 total_sect;
};

static struct drive_struct drive,myparameters;

static int get_drive_parameters()
{
	int cl;
	struct regs regs={0};
	reset_disk_retry(0x80,3);
	regs.eax=0x800;
	regs.edx=DRIVE;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return ELLERR;
	}
	cl=(u8)regs.ecx;
	drive.cylinders=high(((u16)regs.ecx));
	drive.cylinders|=((cl & 0xc0)<<2);
	drive.sectors=cl & 0x3f;
	drive.heads=high(((u16)regs.edx))+1;
	return 0;
}

static int get_chs(u32 LBA)
{
	if(drive.sectors==0)	{
		return ESTATEINV;
	}
	myparameters.sectors=(LBA%(drive.sectors))+1;
	myparameters.cylinders=(LBA/(drive.sectors))/(drive.heads);
	myparameters.heads=(LBA/(drive.sectors))%(drive.heads);
	return 0;
}

static int read_prim(struct node_fs *this, void *buf,u32 LBA,u32 n)
{
	u8 ch,cl;
	u16 c;
	u16 nr;
	struct regs regs={0};
	get_chs(LBA);
	ch=(u8)(myparameters.cylinders);
	cl=myparameters.sectors;
	cl|=(((myparameters.cylinders)&768)>>2);
	c=ch*0x100+cl;
	nr=(u16)n;
	regs.eax=0x0200+nr;
	regs.ebx=0x1000;
	regs.ecx=c;
	regs.edx=(myparameters.heads)*0x100+0x80;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high((u16)regs.eax);
	}
	memcpy(buf,(void *)0x1000,n*0x200);
	return 0;
}

static int write_prim(struct node_fs *this, void *buf, u32 LBA, u32 n)
{
	u8 ch,cl;
	u16 c;
	u16 nr;
	struct regs regs={0};
	get_chs(LBA);
	ch=(u8)(myparameters.cylinders);
	cl=myparameters.sectors;
	cl|=(((myparameters.cylinders)&768)>>2);
	c=ch*0x100+cl;
	nr=(u16)n;
	regs.eax=0x0300+nr;
	regs.ebx=0x1000;
	regs.ecx=c;
	regs.edx=(myparameters.heads)*0x100+0x80;
	regs.f=BD_INT;
	memcpy((void *)0x1000,buf,n*0x200);
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high((u16)regs.eax);
	}
	return 0;
}


struct dap_struct	{
	u8 size;
	u8 rsvd;
	u16 n;
	u32 buf;
	u64 start;
}*dap=(struct dap_struct *)0x9000;

struct drive_parms {
	u16 sz;
	u16 info;
	u32 cyl;
	u32 head;
	u32 sect;
	u64 total_sect;
	u8 pad[200];
}__attribute__((packed));
static struct drive_parms *drive_parms=(struct drive_parms *)0x9100;

static int check_ext()
{
	struct regs regs={0};
	regs.eax=0x4100;
	regs.ebx=0x55aa;
	regs.ecx=0;
	regs.edx=0x80;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.ebx==0xaa55)	{
	} else	{
		return ENOSUP;
	}
	regs.eax=0x4800;
	regs.ebx=0;
	regs.edx=0x80;
	regs.esi=(u32)drive_parms;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1) {
		return ELLERR;
	}
	return 0;
}

static int read_drive(struct node_fs *this, void *buf,u32 LBA,u32 n)
{
	struct regs regs={0};
	if(!buf)
		return EPINV;
	if(LBA>drive_parms->total_sect)
		return EPINV;
	dap->n=(u16)n;
	dap->buf=0x1000;
	dap->start=LBA;
	regs.eax=0x4200;
	regs.ebx=0;
	regs.ecx=0;
	regs.edx=0x80;
	regs.esi=(u32)dap;
	regs.edi=0;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	memcpy(buf,(u8 *)0x1000,(dap->n)*512);
	if(regs.f & 1) {
		return (u16)regs.eax;
	} else {
		return 0;
	}
}

static int write_drive(struct node_fs *this, void *buf,u32 LBA,u32 n)
{
	struct regs regs={0};
	return ENOSUP;
	if(!buf)
		return EPINV;
	dap->n=(u16)n;
	dap->buf=0x1000;
	dap->start=LBA;
	regs.eax=0x4300;
	regs.ebx=0;
	regs.ecx=0;
	regs.edx=0x80;
	regs.esi=(u32)dap;
	regs.edi=0;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	memcpy(buf,(u8 *)0x1000,dap->n*512);
	if(regs.f & 1)
		return (u16)regs.eax;
	else
		return 0;
}

int init_biosdisk(struct node_fs *this);
static int enumerate_volumes(struct node_fs *this);

static struct node_fs hd0_fs = {
	.type = TYPE_FS,
	.name = "hd0",
	.op_unit = 512,
	.flags = 0,
	.count = 0,
	.init = init_biosdisk,
	.deinit = dummy,
	NODE_FS_LIST_INIT,
	NODE_FS_OPS_INIT,
};




int init_biosdisk(struct node_fs *this)
{
	u8 buf[512];
	reset_disk(DRIVE);
	if(!check_ext()) {
		printk("hd0 : %u blocks\n",drive_parms->total_sect);
		drive.total_sect = hd0_fs.count = drive_parms->total_sect;
		hd0_fs.read = read_drive;
		hd0_fs.write = write_drive;
		hd0_fs.type = TYPE_FS_BOTH;
		hd0_fs.op_unit = 512;
	} else {
		printk("hd0: Using legacy read");
		get_drive_parameters();
		printk(" chs : %d,%d,%d\n",drive.cylinders,drive.heads,drive.sectors);
		drive.total_sect = hd0_fs.count = drive.cylinders * drive.heads * drive.sectors;
		hd0_fs.read = read_prim;
		hd0_fs.write = write_prim;
		hd0_fs.type = TYPE_FS_BOTH;
		hd0_fs.op_unit = 512;
	}
	return 0;
}



struct drive_struct fdparams;

static int get_chs_fd(u32 LBA)
{
	fdparams.sectors=(LBA%(18))+1;
	fdparams.cylinders=(LBA/(18))/(2);
	fdparams.heads=(LBA/(18))%(2);
	return 0;
}

static int read_fd(struct node_fs *this, void *buf,u32 LBA,u32 n)
{
	u8 ch,cl;
	u16 c;
	u16 nr;
	struct regs regs={0};
	get_chs_fd(LBA);
	ch=(u8)(fdparams.cylinders);
	cl=fdparams.sectors;
	cl|=(((fdparams.cylinders)&768)>>2);
	c=ch*0x100+cl;
	nr=(u16)n;
	regs.eax=0x0200+nr;
	regs.ebx=0x1000;
	regs.ecx=c;
	regs.edx=(fdparams.heads)*0x100;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high((u16)regs.eax);
	}
	memcpy(buf,(void *)0x1000,n*0x200);
	return 0;
}

static int write_fd(struct node_fs *this, void *buf,u32 LBA,u32 n)
{
	u8 ch,cl;
	u16 c;
	u16 nr;
	struct regs regs={0};
	get_chs_fd(LBA);
	ch=(u8)(fdparams.cylinders);
	cl=fdparams.sectors;
	cl|=(((fdparams.cylinders)&768)>>2);
	c=ch*0x100+cl;
	nr=(u16)n;
	regs.eax=0x0300+nr;
	regs.ebx=0x1000;
	regs.ecx=c;
	regs.edx=(fdparams.heads)*0x100;
	regs.f=BD_INT;
	memcpy((void *)0x1000, buf, n*0x200);
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high((u16)regs.eax);
	}
	return 0;
}

static struct node_fs fd0_fs;

int init_fdbiosdisk(struct node_fs *this)
{
	u8 buf[512];
	if(reset_disk_retry(0, 3))
		return ELLERR;
	fd0_fs.read = read_fd;
	fd0_fs.write = write_fd;
	fd0_fs.count = 2880; //:( Not much support
	return 0;
	
}

static struct node_fs fd0_fs = {
	.type = TYPE_FS,
	.name = "fd0",
	.op_unit = 512,
	.flags = 0,
	.count = 0,
	.init = init_fdbiosdisk,
	.deinit = dummy,
	NODE_FS_LIST_INIT,
	NODE_FS_OPS_INIT,
};


int biosdisk_read(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	return 0;
}

int biosdisk_write(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	switch(offset) {
		case DRIVER_START:
			hd0_fs.init(&hd0_fs);
			fs_link("/class/storage/", &hd0_fs);
			fd0_fs.init(&fd0_fs);
			fs_link("/class/storage/", &fd0_fs);
			return 0;
		default:
			return ENOSUP;
	}
	return 0;
}


struct node_fs biosdisk_driver = {
	.name = "biosdisk_driver",
	.type = TYPE_FS_BOTH,
	NODE_FS_INIT_VARS,
	.class = NULL,
	.read = biosdisk_read,
	.write = biosdisk_write,
	.enumerate = dummy,
	.notify = dummy,
	NODE_FS_INIT_INIT,
	NODE_FS_LIST_INIT,
};

int register_biosdisk()
{
	fs_link("/driver", &biosdisk_driver);
	return 0;
}
